#!/usr/bin/env jruby
# -*- coding: us-ascii -*-

require 'md5'
dir = File.dirname(__FILE__)
require File.join(dir, '..', 'monkey', 'math')
require File.join(dir, 'sortedstable')

########################################

class SimpleHash
  include Enumerable
  
  def initialize(m)
    raise TypeError, 'm must be Integer' unless m.is_a? Integer
    raise RangeError, 'm must be > 0' unless m > 0
    
    @m = m  # number of buckets
    @n = 0  # number of elements in all buckets
    @buckets = Array.new
    m.times { @buckets << Array.new }
  end
  
  attr_reader :m, :n
  
  def alpha
    n / Float(m)
  end
  alias_method :load_factor, :alpha
  
  def add(k, val)
    unless k.respond_to? :to_str or k.respond_to? :to_ary
      raise TypeError, 'key must be String-like or Array-like'
    end
    
    self[k] << val
    @n += 1
    val
  end
  
  def [](k)
    # explicitly do Array#join in case Array#to_s has been hacked
    k = k.join('') if k.respond_to? :to_ary
    h = Math.h(MD5.new(k).digest.hash, @m)
    @buckets[h]
  end
  private '[]'.intern
  
  def clear
    @buckets.clear
  end
  
  def each
    @buckets.each { |bucket| yield bucket }
  end
  
  def to_a
    @buckets
  end
  
  def to_hash
    hash = {}
    @buckets.each_with_index { |b, i| hash[i] = b }
    hash
  end
  
  def to_s
    @buckets.to_s
  end
  
  def inspect
    @buckets.inspect
  end
end

########################################

class SimpleGroups < SimpleHash
  def uniq!
    @buckets.each { |bucket| bucket.uniq! }
    @n = @buckets.inject(0) { |tot, buck| tot += buck.size }
    self
  end
  
  def at_least_size(number=2)
    @buckets.select { |bucket| bucket.size >= number }
  end
  
  def sort
    @buckets.sort_by { |bucket| [bucket.size, bucket] }
  end
  
  def smallest(number=1)
    ss = SortedStable.new(number, lambda { |b| [-b.size, b] })
    @buckets.each { |bucket| ss << bucket }
    ss.to_a
  end
  
  def largest(number=1)
    ss = SortedStable.new(number, lambda { |b| [b.size, b] })
    @buckets.each { |bucket| ss << bucket }
    ss.to_a
  end
  
  def min
    smallest(1).first.size
  end
  
  def max
    largest(1).first.size
  end
  
  def all_groups!(&block)
    yield @buckets.pop until @buckets.empty?
  end
end

########################################

class GroupsOfPaths < SimpleGroups
  def path_groups
    result = to_a.uniq
    result.each { |paths| paths.sort! }
    result
  end
end

########################################

if $0 == __FILE__
  dir = File.dirname(__FILE__)
  require File.join(dir, '..', '..', 'test', 'gibberish')
  
  include Gibberish
  
  s = wordn(8)
  a = s.split('')
  puts MD5.new(s).digest == MD5.new(a.join('')).digest
  
  puts '=========='
  
  groups = SimpleGroups.new(23)
  
  30.times do
    word = wordn(8)
    groups.add(word, word)
  end
  
  p groups
  p [groups.n, groups.m, groups.alpha]
  p [groups.min, groups.max]
  p groups.at_least_size(2)
  p groups.sort
  p groups.smallest(5)
  p groups.largest
end
